#include "pythonObjects.h"
#include "utils.h"
#include "pythonXFunctions.h"
#include "exceptions.h"
#include "Instruction.h"

namespace s2e {
  namespace plugins {
    namespace python {

      void Instruction_dealloc(PyObject* self) {
        std::cout << std::flush;
        delete PyInstruction_AsInstruction(self);
        Py_TYPE(self)->tp_free((PyObject*)self);
      }

     static PyObject* Instruction_getOpcode(PyObject* self, PyObject* noarg) {
        try {
          const s2e::uint8* opcode = PyInstruction_AsInstruction(self)->getOpcode();
          s2e::uint32 size         = PyInstruction_AsInstruction(self)->getSize();
          return PyBytes_FromStringAndSize(reinterpret_cast<const char*>(opcode), size);
        }
        catch (const s2e::exceptions::Exception& e) {
          return PyErr_Format(PyExc_TypeError, "%s", e.what());
        }
      }

     static PyObject* Instruction_getAddress(PyObject* self, PyObject* noarg) {
       try {
         return PyLong_FromUint64(PyInstruction_AsInstruction(self)->getAddress());
       }
       catch (const s2e::exceptions::Exception& e) {
         return PyErr_Format(PyExc_TypeError, "%s", e.what());
       }
     }

      static PyObject* Instruction_getSize(PyObject* self, PyObject* noarg) {
        try {
          return PyLong_FromUint32(PyInstruction_AsInstruction(self)->getSize());
        }
        catch (const s2e::exceptions::Exception& e) {
          return PyErr_Format(PyExc_TypeError, "%s", e.what());
        }
      }

      static PyObject* Instruction_setOpcode(PyObject* self, PyObject* opc) {
        try {
          if (!PyBytes_Check(opc))
            return PyErr_Format(PyExc_TypeError, "Instruction::setOpcode(): Expected bytes as argument.");

          s2e::uint8* opcode = reinterpret_cast<s2e::uint8*>(PyBytes_AsString(opc));
          s2e::uint32 size   = static_cast<s2e::uint32>(PyBytes_Size(opc));

          PyInstruction_AsInstruction(self)->setOpcode(opcode, size);
          Py_INCREF(Py_None);
          return Py_None;
        }
        catch (const s2e::exceptions::Exception& e) {
          return PyErr_Format(PyExc_TypeError, "%s", e.what());
        }
      }

      static int Instruction_print(PyObject* self, void* io, int s) {
        std::cout << PyInstruction_AsInstruction(self);
        return 0;
      }

      static PyObject* Instruction_str(PyObject* self) {
        try {
          std::stringstream str;
          str << PyInstruction_AsInstruction(self);
          return PyStr_FromFormat("%s", str.str().c_str());
        }
        catch (const s2e::exceptions::Exception& e) {
          return PyErr_Format(PyExc_TypeError, "%s", e.what());
        }
      }


      //! Instruction methods.
      PyMethodDef Instruction_callbacks[] = {
    		  {"getAddress",                 Instruction_getAddress,              METH_NOARGS,     ""},
			  {"getOpcode",                 Instruction_getOpcode,                METH_NOARGS,     ""},
			  {"getSize",                   Instruction_getSize,                  METH_NOARGS,     ""},
			  {"setOpcode",                 Instruction_setOpcode,                METH_O,          ""},
			  {nullptr,                     nullptr,                              0,               nullptr}
      };


      PyTypeObject Instruction_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "Instruction",                              /* tp_name */
        sizeof(Instruction_Object),                 /* tp_basicsize */
        0,                                          /* tp_itemsize */
        (destructor)Instruction_dealloc,            /* tp_dealloc */
        (printfunc)Instruction_print,               /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_compare */
        (reprfunc)Instruction_str,                  /* tp_repr */
        0,                                          /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        0,                                          /* tp_hash */
        0,                                          /* tp_call */
        (reprfunc)Instruction_str,                  /* tp_str */
        0,                                          /* tp_getattro */
        0,                                          /* tp_setattro */
        0,                                          /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,                         /* tp_flags */
        "Instruction objects",                      /* tp_doc */
        0,                                          /* tp_traverse */
        0,                                          /* tp_clear */
        0,                                          /* tp_richcompare */
        0,                                          /* tp_weaklistoffset */
        0,                                          /* tp_iter */
        0,                                          /* tp_iternext */
        Instruction_callbacks,                      /* tp_methods */
        0,                                          /* tp_members */
        0,                                          /* tp_getset */
        0,                                          /* tp_base */
        0,                                          /* tp_dict */
        0,                                          /* tp_descr_get */
        0,                                          /* tp_descr_set */
        0,                                          /* tp_dictoffset */
        0,                                          /* tp_init */
        0,                                          /* tp_alloc */
        0,                                          /* tp_new */
        0,                                          /* tp_free */
        0,                                          /* tp_is_gc */
        0,                                          /* tp_bases */
        0,                                          /* tp_mro */
        0,                                          /* tp_cache */
        0,                                          /* tp_subclasses */
        0,                                          /* tp_weaklist */
        0,                                          /* tp_del */
        0,                                          /* tp_version_tag */
        0,                                          /* tp_finalize */
      };


      PyObject* PyInstruction(void) {
        Instruction_Object* object;

        PyType_Ready(&Instruction_Type);
        object = PyObject_NEW(Instruction_Object, &Instruction_Type);
        if (object != NULL)
          object->inst = new s2e::plugins::Instruction();

        return (PyObject*)object;
      }


      PyObject* PyInstruction(const s2e::uint8* opcode, s2e::uint32 opSize) {
        Instruction_Object* object;

        PyType_Ready(&Instruction_Type);
        object = PyObject_NEW(Instruction_Object, &Instruction_Type);
        if (object != NULL)
          object->inst = new s2e::plugins::Instruction(opcode, opSize);

        return (PyObject*)object;
      }


      PyObject* PyInstruction(const s2e::plugins::Instruction& inst) {
        Instruction_Object* object;

        PyType_Ready(&Instruction_Type);
        object = PyObject_NEW(Instruction_Object, &Instruction_Type);
        if (object != NULL)
          object->inst = new s2e::plugins::Instruction(inst);

        return (PyObject*)object;
      }

    };
  };
};
